package io.netty.util.internal;

import java.io.IOException;
import java.net.*;
import java.nio.channels.DatagramChannel;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.Enumeration;

public final class SocketUtils {

    private SocketUtils() {

    }

    public static void connect(final Socket socket, final SocketAddress remoteAddress,final int timeout)
            throws IOException {
        try {
            AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
                socket.connect(remoteAddress,timeout);
                return null;
            });
        } catch (PrivilegedActionException e) {
            throw (IOException)e.getCause();
        }
    }

    public static void bind(final Socket socket,final SocketAddress bindPoint) throws IOException {
        try {
            AccessController.doPrivileged((PrivilegedExceptionAction<Void>)() -> {
                socket.bind(bindPoint);
                return null;
            });
        } catch (PrivilegedActionException e) {
            throw (IOException)e.getCause();
        }
    }

    public static boolean connect(final SocketChannel socketChannel,final SocketAddress remoteAddress) throws IOException {
        try {
            return AccessController.doPrivileged((PrivilegedExceptionAction<Boolean>) () -> socketChannel.connect(remoteAddress));
        } catch (PrivilegedActionException e) {
            throw (IOException)e.getCause();
        }
    }

    public static void bind(final SocketChannel socketChannel, final SocketAddress address) throws IOException {
        try {
            AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
                socketChannel.bind(address);
                return null;
            });
        } catch (PrivilegedActionException e) {
            throw (IOException) e.getCause();
        }
    }

    public static SocketChannel accept(final ServerSocketChannel serverSocketChannel) throws IOException {
        try {
            return AccessController.doPrivileged((PrivilegedExceptionAction<SocketChannel>) serverSocketChannel::accept);
        } catch (PrivilegedActionException e) {
            throw (IOException) e.getCause();
        }
    }

    public static void bind(final DatagramChannel networkChannel, final SocketAddress address) throws IOException {
        try {
            AccessController.doPrivileged((PrivilegedExceptionAction<Void>) () -> {
                networkChannel.bind(address);
                return null;
            });
        } catch (PrivilegedActionException e) {
            throw (IOException) e.getCause();
        }
    }

    public static SocketAddress localSocketAddress(final ServerSocket socket) {
        return AccessController.doPrivileged((PrivilegedAction<SocketAddress>) socket::getLocalSocketAddress);
    }

    public static InetAddress addressByName(final String hostname) throws UnknownHostException {
        try {
            return AccessController.doPrivileged((PrivilegedExceptionAction<InetAddress>) () -> InetAddress.getByName(hostname));
        } catch (PrivilegedActionException e) {
            throw (UnknownHostException) e.getCause();
        }
    }

    public static InetAddress[] allAddressesByName(final String hostname) throws UnknownHostException {
        try {
            return AccessController.doPrivileged((PrivilegedExceptionAction<InetAddress[]>) () -> InetAddress.getAllByName(hostname));
        } catch (PrivilegedActionException e) {
            throw (UnknownHostException) e.getCause();
        }
    }

    public static InetSocketAddress socketAddress(final String hostname, final int port) {
        return AccessController.doPrivileged((PrivilegedAction<InetSocketAddress>) () -> new InetSocketAddress(hostname, port));
    }

    public static Enumeration<InetAddress> addressesFromNetworkInterface(final NetworkInterface intf) {
        return AccessController.doPrivileged((PrivilegedAction<Enumeration<InetAddress>>) intf::getInetAddresses);
    }

    public static InetAddress loopbackAddress() {
        return AccessController.doPrivileged((PrivilegedAction<InetAddress>) () -> {
           if (PlatformDependent.javaVersion() >= 7) {
               return InetAddress.getLoopbackAddress();
           }

           try {
               return InetAddress.getByName(null);
           } catch (UnknownHostException e) {
               throw new IllegalStateException(e);
           }
        });
    }

    public static byte[] hardwareAddressFromNetworkInterface(final NetworkInterface intf) throws SocketException {
        try {
            return AccessController.doPrivileged((PrivilegedExceptionAction<byte[]>) intf::getHardwareAddress);
        } catch (PrivilegedActionException e) {
            throw (SocketException)e.getCause();
        }
    }
}
